/**
 * Copyright (c) 2008, Mu Dynamics.
 *  All rights reserved.
 *  
 *   Redistribution and use in source and binary forms, with or without modification, 
 *   are permitted provided that the following conditions are met:
 *   
 *  - Redistributions of source code must retain the above copyright notice, 
 *     this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice, 
 *     this list of conditions and the following disclaimer in the documentation and/or 
 *     other materials provided with the distribution.
 *  - Neither the name of the "Mu Dynamics" nor the names of its contributors may be used 
 *     to endorse  or promote products derived from this software without specific prior 
 *     written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.mu.jacob.core.generator;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;

import org.apache.log4j.Logger;

import com.google.inject.Inject;
import com.mu.jacob.core.builder.AbstractBuilder;
import com.mu.jacob.core.builder.IBuilder;
import com.mu.jacob.core.builder.IModelContext;
import com.mu.jacob.core.model.IModel;
import com.mu.jacob.core.model.Model;
import com.sun.tools.javac.jvm.Target;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.DefaultFileManager;

/**
 * Compiler processor for Jacob
 * @author Adam Smyczek
 */
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class JacobProcessor extends AbstractProcessor {

	/* Round counter, jacob used first round only */
	private int rounds = 0; 
	
	/* Builder list */
	private List<IBuilder> builders = null;
	
	/* Injected config */
	@Inject
	private Config config;

	/* and model */
	@Inject
	private IModel model;
	
	/**
	 * Main Jacob process method
	 */
	public void process(List<IBuilder> builders) {
		this.builders = builders;
		compile(getOptions());
	}

	/**
	 * Process called from Java compiler
	 */
	@Override
	public boolean process(Set<? extends TypeElement> typeElements, RoundEnvironment roundEnv) {
		// process models only in the first round
		if (rounds == 0) {
			// Set environments to model
			((Model)model).initEnvironment(processingEnv, roundEnv);
			try {
				// process
				for (IBuilder builder : builders) {
					// init context in case of IModelContext
					if (builder instanceof AbstractBuilder) {
						for (Object context : ((AbstractBuilder)builder).getContextMap().values()) {
							if (context instanceof IModelContext) {
								((IModelContext)context).initContext(model);
							}
						}
					}
					
					// Initialize renderer and build
					builder.initRenderer();
					builder.build(model);
				}
			} catch (Exception e) {
				logger.error("Processor exception!", e);
			}
		}
		rounds++;
		
		return true;
	}

	/**
	 * Processor compile function, initializes and runs Java compiler.
	 * @param options
	 */
	private void compile(String... options) {
		com.sun.tools.javac.main.Main compiler = new com.sun.tools.javac.main.Main("jacob");

		// init
		Context context = new Context();
		DefaultFileManager.preRegister(context);

		// compile
		int status = compiler.compile(
				options,
				context,
				com.sun.tools.javac.util.List.<JavaFileObject>nil(),
				Arrays.<Processor>asList(new Processor[] { this })
		);
	
		if (status != 0) {
			logger.error("Error processing model!");
		}
	}
	
	/**
	 * @return compiler options
	 */
	private String[] getOptions() {
		
		List<String> params = new ArrayList<String>();
		
		// set target to JDK 1.5
		params.add("-target");
		params.add(Target.JDK1_6.name);

		// Process only annotations
		params.add("-proc:only");

		// add classpath
		if (config.getClassPath() != null) {
			params.add("-classpath");
			params.add(config.getClassPath());
		}
		
		// add files
		for (String fileName : getAbsolutePathModelFiles()) {
			params.add(fileName);
		}
		
		return params.toArray(new String[params.size()]);
	}
	
	/**
	 * @return absolute paths for all model classes
	 */
	public List<String> getAbsolutePathModelFiles() {
		List<String> result = new ArrayList<String>();
		for (ModelSet modelSet : config.getModelSets()) {
			String path = modelSet.getDir().getPath() + File.separator;
			for (String fileName : modelSet.getFileNames()) {
				result.add(path + fileName);
			}
		}
		return result;
	}
	
	private static Logger logger = Logger.getLogger(JacobProcessor.class);
	
}
